#include "TwApplicationPCH.h"
#include "TwApplication.h"
#include "TwApplicationHeaders.h"
#include <limits>

TwApplication::TwApplication()
: m_mCamera(NULL)
, m_mScene(NULL)
, m_mZDefaultEnable(D3DZB_TRUE)
, m_mFillMode(D3DFILL_SOLID)
, m_mLastTime(0)
, m_mCurrentTime(0)
, m_mFrameTime(0)
, m_mAccumTime(0)
, m_mPause(false)
, m_mAccumTimeWithPause(0)
, m_mMinFramePeriod(0)
, m_mLastFrame(0)
, m_mOrthoCamera(NULL)
, m_mReferenceWindow(NULL)
{

}

TwApplication::~TwApplication()
{

}

bool TwApplication::initialize(HWND wnd)
{
	m_mReferenceWindow = wnd;

	if (!createDevice())
		return false;

	if (!createCamera())
		return false;

	if (!createScene())
		return false;

	if (!_setScreenSpaceCamera())
		return false;

	return true;
}

void TwApplication::terminate()
{
	for (TwScreenTwementIterator i = m_mScreenTwements.begin(); i != m_mScreenTwements.end(); ++i)
	{
		TwSafeDelete(*i);
	}
	m_mScreenTwements.clear();

	TwSafeDelete(m_mScene);

	TwShutDownSingleton(TwNameGenerator);
	TwShutDownSingleton(TwMeshManager);
	TwShutDownSingleton(TwSkeletonManager);
	TwShutDownSingleton(TwMaterialManager);
	TwShutDownSingleton(TwDeviceManager);
}

void TwApplication::process()
{
	// MeasureTime returns false if the frame rate is
	// over the pre-set limit
	if (!_measureTime())
		return;

	beginFrame();

	beginUpdate();
	update();
	endUpdate();

	beginRender();
	render();
	endRender();

	displayFrame();
	endFrame();
}

void TwApplication::beginFrame()
{

}

void TwApplication::beginUpdate()
{

}

void TwApplication::update()
{
	if (m_mScene)
		m_mScene->update();
}

void TwApplication::endUpdate()
{

}

void TwApplication::beginRender()
{
	IDirect3DDevice9* d3dDevice = TwDeviceManager::getDevice();
	d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(192, 192, 192), 1.0f, 0);

	d3dDevice->SetRenderState(D3DRS_ZENABLE, m_mZDefaultEnable);

	d3dDevice->SetRenderState(D3DRS_FILLMODE, m_mFillMode);

	// enable alpha blending
	d3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	d3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	d3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	d3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	d3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
	d3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
}

void TwApplication::render()
{
	renderScene();
	renderScreenItems();
}

void TwApplication::endRender()
{
	
}

void TwApplication::renderScene()
{
	if (m_mScene)
		m_mScene->render(getCurrentTime());
}

void TwApplication::renderScreenItems()
{
	IDirect3DDevice9* d3dDevice = TwDeviceManager::getDevice();
	if (SUCCEEDED(d3dDevice->BeginScene()))
	{
		D3DXMATRIX m;
		d3dDevice->SetTransform(D3DTS_WORLD, D3DXMatrixIdentity(&m));

		// disable lighting to ignore any materials
		d3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

		// set texture filter
		d3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		d3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);

		d3dDevice->SetFVF(TwScreenTwement::VERTEX::FVF);

		_useScreenSpaceCamera();

		_renderScreenTwements();

		d3dDevice->EndScene();

		// reset render states
		d3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);

		d3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		d3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	}
}

void TwApplication::displayFrame()
{
	IDirect3DDevice9* d3dDevice = TwDeviceManager::getDevice();
	d3dDevice->Present(NULL, NULL, NULL, NULL);
}

void TwApplication::endFrame()
{

}

void TwApplication::setPause(bool pause)
{
	m_mPause = pause;
}

bool TwApplication::getPause() const
{
	return m_mPause;
}

TimeValue TwApplication::getCurrentTime(bool ignorePaused /* = false */) const
{
	return !ignorePaused ? m_mAccumTimeWithPause : m_mAccumTime;
}

TwCamera* TwApplication::getCamera()
{
	return m_mCamera;
}

TwSceneManager* TwApplication::getScene()
{
	return m_mScene;
}

DWORD TwApplication::getFillMode() const
{
	return m_mFillMode;
}

void TwApplication::setFillMode(DWORD fm)
{
	m_mFillMode = fm;
}

void TwApplication::addScreenTwement(TwScreenTwement* element)
{
	if (element)
		m_mScreenTwements.push_back(element);
}

HWND TwApplication::getReferenceWindow() const
{
	return m_mReferenceWindow;
}

bool TwApplication::createDevice()
{
	if (!TwDeviceManager::getSingleton().createDevice(m_mReferenceWindow))
		return false;
}

bool TwApplication::createCamera()
{
	TwSafeDelete(m_mCamera);
	m_mCamera = TwNew TwCamera;

	const D3DSURFACE_DESC& backBufferDesc = TwDeviceManager::getSingleton().getBackBufferDesc();
	float aspect = (float)backBufferDesc.Width / (float)backBufferDesc.Height;
	m_mCamera->setProjectionParameters(TwCamera::PT_PERSPECTIVE, D3DX_PI / 4, aspect, 0.1f, 500.0f);

	return true;
}

bool TwApplication::createScene()
{
	TwSafeDelete(m_mScene);
	m_mScene = TwNew TwSceneManager;

	// Set mCamera as the scene's default camera
	m_mScene->setCamera(m_mCamera);

	return true;
}

bool TwApplication::_measureTime()
{
	if (m_mLastTime == 0)
	{
		m_mLastTime = GetTickCount();
		m_mAccumTime = 0;
		m_mAccumTimeWithPause = 0;
	}

	// measure time
	m_mCurrentTime = GetTickCount();
	TimeValue deltaTime = m_mCurrentTime - m_mLastTime;
	if (deltaTime < 0)
		deltaTime = 0;
	m_mLastTime = m_mCurrentTime;
	m_mAccumTime += deltaTime;
	if (!m_mPause)
		m_mAccumTimeWithPause += deltaTime;

	// frame rate limiter
	if (m_mAccumTime < m_mLastFrame + m_mMinFramePeriod)
		return false;

	m_mFrameTime = m_mAccumTime - m_mLastFrame;
	m_mLastFrame = m_mAccumTime;

	return true;
}

bool TwApplication::_setScreenSpaceCamera()
{
	if (!m_mOrthoCamera)
		m_mOrthoCamera = TwNew TwCamera;

	const D3DSURFACE_DESC& backBufferDesc = TwDeviceManager::getSingleton().getBackBufferDesc();
	m_mOrthoCamera->setProjectionParameters(TwCamera::PT_ORTHOGRAPHIC, backBufferDesc.Width, backBufferDesc.Height, 0.0f, 1.0f);
	m_mOrthoCamera->setPosition(D3DXVECTOR3(0.0f, 0.0f, 0.0f));

	return true;
}

void TwApplication::_useScreenSpaceCamera()
{
	if (m_mOrthoCamera)
	{
		IDirect3DDevice9* d3dDevice = TwDeviceManager::getDevice();
		d3dDevice->SetTransform(D3DTS_VIEW, &m_mOrthoCamera->getViewMatrix());
		d3dDevice->SetTransform(D3DTS_PROJECTION, &m_mOrthoCamera->getProjectionMatrix());
	}
}

void TwApplication::_renderScreenTwements()
{
	// render each screen element in the list
	TwScreenTwementIterator i, iend;
	iend = m_mScreenTwements.end();
	for (i = m_mScreenTwements.begin(); i != iend; ++i)
		(*i)->render();
}
